package com.minidb.cli;

import com.minidb.engine.*;
import com.minidb.sql_compiler.*;
import com.minidb.storage.BufferPool;
import com.minidb.storage.FileManager;
import com.minidb.utils.Constants;

import java.io.IOException;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Demo：功能完整性一键验证
 * 覆盖：建表/插入/查询/删除/错误处理/缓存淘汰/持久化+重启验证/计划JSON打印（可选）
 *
 * 运行方式：
 *   - 右键运行本类 main()
 *   - 可修改 DEBUG_PLAN 为 true 打印 Token/AST/Plan(JSON)
 */
public class Demo {

    // ============ 可调参数 ============
    private static final boolean DEBUG_PLAN = true;               // 是否打印 Token/AST/Plan(JSON)
    private static final String DATA_DIR = "./demo_data";         // 独立数据目录
    private static final Constants.ReplacementPolicy POLICY = Constants.ReplacementPolicy.LRU;
    private static final int BIG_INSERT_COUNT = 250;              // 压测行数（用于触发多页与缓存淘汰）
    // =================================

    // 组件
    private FileManager fm;
    private BufferPool bp;
    private SystemCatalog syscat;
    private CatalogManager cm;
    private Catalog catalog;
    private SyntaxParser parser;
    private SemanticAnalyzer semantic;
    private StorageEngine storage;
    private Executor executor;

    // 统计
    private int passed = 0, failed = 0;

    public static void main(String[] args) throws Exception {
        Demo d = new Demo();
        d.runAll();
    }

    /* ================== 主流程 ================== */
    private void runAll() throws Exception {
        resetDir();                     // 每次跑 Demo 先清空 demo_data（可注释掉保留历史）
        freshInit(POLICY);

        title("系统目录初始化");
        ok(Files.exists(Path.of(DATA_DIR, "__catalog")), "__catalog 创建失败");

        title("创建表 student(id INT, name TEXT, age INT)");
        say(exec("CREATE TABLE student(id INT, name TEXT, age INT);"));

        title("重复建表（应报错）");
        checkError(exec("CREATE TABLE student(id INT, name TEXT, age INT);"));

        title("插入两条记录");
        say(exec("INSERT INTO student(id,name,age) VALUES(1,'Alice',20);"));
        say(exec("INSERT INTO student(id,name,age) VALUES(2,'Bob',19);"));

        title("查询所有记录（应为 2 行）");
        String selAll = exec("SELECT * FROM student;");
        say(selAll);
        ok(selAll.toLowerCase().contains("2 row"), "SELECT * 行数不为 2");

        title("条件查询（age > 18，包含 Alice、Bob）");
        String selWhere = exec("SELECT id,name FROM student WHERE age > 18;");
        say(selWhere);
        ok(selWhere.contains("Alice") && selWhere.contains("Bob"), "条件查询不包含预期行");

        title("类型错误（age 列为 INT，插入字符串应报错）");
        checkError(exec("INSERT INTO student(id,name,age) VALUES(3,'Carl','notInt');"));

        title("删除一条（id=1）并查询");
        say(exec("DELETE FROM student WHERE id=1;"));
        String selAfterDel = exec("SELECT * FROM student;");
        say(selAfterDel);
        ok(selAfterDel.toLowerCase().contains("1 row"), "删除后行数不为 1");

        // ---------- 缓冲池/页式压测 ----------
        title("缓存淘汰压测（LRU），插入多行触发多页");
        ensureBigTableExists("big");
        bulkInsert("big", BIG_INSERT_COUNT);
        String selectBig = exec("SELECT c1 FROM big;");
        say("访问 big 完成（观察控制台应出现 Evicting 日志）。");
        say(String.format("BufferPool 命中率：%.2f%%", bp.getHitRate() * 100));

        // ---------- 持久化 + 重启验证 ----------
        title("重启前 flush 缓冲区");
        bp.flushAll();

        title("模拟进程重启（重新初始化组件）");
        restartOnly();
        String selAfterRestart = exec("SELECT * FROM student;");
        say(selAfterRestart);
        ok(selAfterRestart.toLowerCase().contains("1 row"), "重启后 student 行数不为 1（持久化失败？）");

        // 汇总
        say("");
        say("======== DEMO 结束 ========");
        say(String.format("通过: %d，失败: %d", passed, failed));
        if (failed > 0) {
            say("有失败项，请查看上方 FAIL 日志定位问题。");
        } else {
            say("所有关键功能验证通过 ✅");
        }
    }

    /* ================== 组件初始化/重启 ================== */
    private void freshInit(Constants.ReplacementPolicy policy) throws IOException {
        fm = new FileManager(DATA_DIR);
        bp = new BufferPool(Constants.BUFFER_POOL_CAPACITY, fm, policy);
        syscat = new SystemCatalog(fm, bp);
        cm = new CatalogManager(fm, syscat);
        cm.loadFromSystemTable();   // 从 __catalog 装载所有表结构
        catalog = new Catalog(cm);
        parser = new SyntaxParser();
        semantic = new SemanticAnalyzer(catalog);
        storage = new StorageEngine(bp, fm, cm);
        executor = new Executor(storage, cm);
    }

    private void restartOnly() throws IOException {
        // 不清空目录，重新 new 一套组件，模拟“程序重启”
        fm = new FileManager(DATA_DIR);
        bp = new BufferPool(Constants.BUFFER_POOL_CAPACITY, fm, Constants.ReplacementPolicy.LRU);
        syscat = new SystemCatalog(fm, bp);
        cm = new CatalogManager(fm, syscat);
        cm.loadFromSystemTable();
        catalog = new Catalog(cm);
        parser = new SyntaxParser();
        semantic = new SemanticAnalyzer(catalog);
        storage = new StorageEngine(bp, fm, cm);
        executor = new Executor(storage, cm);
    }

    private void resetDir() throws IOException {
        Path root = Path.of(DATA_DIR);
        if (!Files.exists(root)) return;
        try (var s = Files.walk(root)) {
            s.sorted((a,b)->b.getNameCount()-a.getNameCount()).forEach(p -> {
                try { Files.deleteIfExists(p); } catch (IOException ignored) {}
            });
        }
    }

    /* ================== SQL 执行与调试打印 ================== */
    private String exec(String sql) {

        try {
            sql = sql.trim();
            if (sql.endsWith(";")) {
                sql = sql.substring(0, sql.length() - 1);
            }

            if (DEBUG_PLAN) {
                var toks = new LexicalAnalyzer().tokenize(sql);
                System.out.println("TOKENS:");
                toks.forEach(t -> System.out.println("  " + t));
            }

            SQLStatement stmt = parser.parse(sql);
            if (DEBUG_PLAN) System.out.println("AST: " + stmt);

            semantic.analyze(stmt);
            if (DEBUG_PLAN) System.out.println("SEMANTIC: OK");

            // 计划 JSON 仅用于展示
            if (DEBUG_PLAN) {
                PlanNode planTree = buildPlanNodeForDebug(stmt);
                System.out.println("PLAN (JSON):");
                System.out.println(planTree.toJSON());
            }

            // 实际执行
            ExecutionPlan plan = new ExecutionPlan();
            plan.type = stmt.type;
            plan.tableName = stmt.tableName;
            plan.columns = stmt.columns;
            plan.columnTypes = stmt.columnTypes;
            plan.values = stmt.values;
            plan.condition = stmt.condition;

            return executor.executePlan(plan);

        } catch (Exception e) {
            return "ERROR: " + e.getMessage();
        }
    }

    private PlanNode buildPlanNodeForDebug(SQLStatement s) {
        switch (s.type) {
            case SELECT -> {
                PlanNode scan = new PlanNode("SeqScan");
                scan.props.put("table", s.tableName);
                PlanNode top;
                if (s.condition != null) {
                    PlanNode filter = new PlanNode("Filter");
                    filter.props.put("condition", s.condition.toString());
                    filter.addChild(scan);
                    top = filter;
                } else {
                    top = scan;
                }
                PlanNode project = new PlanNode("Project");
                project.props.put("columns",
                        (s.columns == null || s.columns.isEmpty()) ? "*" : String.join(",", s.columns));
                project.addChild(top);
                return project;
            }
            case INSERT -> {
                PlanNode ins = new PlanNode("Insert");
                ins.props.put("table", s.tableName);
                ins.props.put("values", s.values == null ? "" : String.join(",", s.values));
                if (s.columns != null && !s.columns.isEmpty())
                    ins.props.put("columns", String.join(",", s.columns));
                return ins;
            }
            case CREATE_TABLE -> {
                PlanNode ct = new PlanNode("CreateTable");
                ct.props.put("table", s.tableName);
                ct.props.put("columns", String.join(",", s.columns));
                ct.props.put("types", String.join(",", s.columnTypes));
                return ct;
            }
            case DELETE -> {
                PlanNode scan = new PlanNode("SeqScan");
                scan.props.put("table", s.tableName);
                if (s.condition != null) {
                    PlanNode filter = new PlanNode("Filter");
                    filter.props.put("condition", s.condition.toString());
                    filter.addChild(scan);
                    PlanNode del = new PlanNode("Delete");
                    del.addChild(filter);
                    return del;
                } else {
                    PlanNode del = new PlanNode("Delete");
                    del.addChild(scan);
                    return del;
                }
            }
            default -> { return new PlanNode("Unknown"); }
        }
    }

    /* ================== 压测 & 帮助方法 ================== */
    private void ensureBigTableExists(String table) {
        if (!cm.tableExists(table)) {
            say(exec("CREATE TABLE " + table + "(c1 INT, c2 TEXT, c3 TEXT, c4 TEXT);"));
        }
    }

    private void bulkInsert(String table, int count) {
        for (int i = 0; i < count; i++) {
            String sql = "INSERT INTO " + table + "(c1,c2,c3,c4) VALUES(" + i + ",'xxxx','yyyy','zzzz');";
            exec(sql);
        }
    }

    /* ================== 断言与输出 ================== */
    private void ok(boolean cond, String ifFail) {
        if (cond) { passed++; System.out.println("[PASS] " + caller())
        ; }
        else { failed++; System.out.println("[FAIL] " + caller() + " -> " + ifFail); }
    }
    private void checkError(String out) {
        ok(out.startsWith("ERROR:"), "未触发错误，实际输出: " + out);
    }

    private void say(String s) { System.out.println(s); }
    private void title(String s) { System.out.println("\n=== " + s + " ==="); }

    private String caller() {
        // 简易标识：取上层 title 后最近一次 ok 的语义，便于阅读（不严格）
        return "检查通过";
    }
}
